#! /usr/bin/env python
# encoding: utf-8

import re, os
import ibexutils
from waflib import Logs

class Operator (object):
	unary_fmt_in = "class UnaryOperator<(?P<N>.+?),(?P<X>.+?),(?P<Y>.+?)>"
	binary_fmt_in = "class BinaryOperator<(?P<N>.+?),(?P<X1>.+?),(?P<X2>.+?),(?P<Y>.+?)>"
	cppname_fmt_in = "extern\s+const\s+char\s+{N}\[\]\s+=\s+\"(.+?)\""

	header_fmt_out = "#include \"{headername}\""
	macro_unary_fmt_out = "ADD_UNARY_OPERATOR({N},{X},{Y});"
	macro_binary_fmt_out = "ADD_BINARY_OPERATOR({N},{X1},{X2},{Y});"
	inline_unary_fmt_out = "inline {Y} {name}(const {X}& x) {{ return UnaryOperator<{N},{X},{Y}>::fwd(x); }}"
	inline_binary_fmt_out = "inline {Y} {name}(const {X1}& x1, const {X2}& x2) {{ return BinaryOperator<{N},{X1},{X2},{Y}>::fwd(x1,x2); }}"

	_all_ops = []

	def __init__ (self, headerfile):
		self.ok = True
		self.hfile = headerfile

		self._check_cpp()

		if self.ok:
			self._parse_header ()

		if self.ok:
			self._parse_cpp ()

		if self.ok:
			self._all_ops.append (self.data)

	def _check_cpp (self):
		cppname = self.hfile.name[:-1] + "cpp"
		self.cppfile = self.hfile.parent.make_node (cppname)
		self.ok = self.cppfile.exists()
		if not self.ok:
			Logs.warn ("No cpp file found for %s" % self.hfile.srcpath())

	def _parse_header (self):
		unary_re = re.compile (self.unary_fmt_in)
		binary_re = re.compile (self.binary_fmt_in)

		self.data = {}
		with open (self.hfile.abspath(), "r") as f:
			for l in ibexutils.to_unicode(f.read()).splitlines():
				mu = unary_re.match (l)
				mb = binary_re.match (l)
				if mu:
					self.data = dict (mu.groupdict()) # copy
					self.data["type"] = "unary"
					break
				elif mb:
					self.data = dict (mb.groupdict()) # copy
					self.data["type"] = "binary"
					break
		self.ok = bool (self.data)
		if not self.ok:
			Logs.warn ("No Operator class found in %s" % self.hfile.srcpath())
		else:
			self.data["headername"] = self.hfile.name

	def _parse_cpp (self):
		cppname_re = re.compile (self.cppname_fmt_in.format (**self.data))

		with open (self.cppfile.abspath(), "r") as f:
			for l in ibexutils.to_unicode(f.read()).splitlines():
				m = cppname_re.match (l)
				if m:
					self.data["name"] = m.group (1)
					break
		self.ok = "name" in self.data
		if not self.ok:
			Logs.warn ("No char definition found in %s" % self.cppfile.srcpath())
		else:
			self.data["cppfile"] = self.cppfile

	@classmethod
	def set_env (cls, conf):
		gen = (d["headername"] for d in cls._all_ops)
		conf.env.append_unique ("OPERATORS_HEADERS", gen)

		gen = (cls.header_fmt_out.format (**data) for data in cls._all_ops)
		conf.env.OPERATORS_INCLUDES = os.linesep.join (gen)

		unary_ops = [ d for d in cls._all_ops if d["type"] == "unary" ]
		binary_ops = [ d for d in cls._all_ops if d["type"] == "binary" ]

		gen = (cls.macro_unary_fmt_out.format (**data) for data in unary_ops)
		conf.env.OPERATORS_MACRO_UNARY = os.linesep.join (gen)

		gen = (cls.macro_binary_fmt_out.format (**data) for data in binary_ops)
		conf.env.OPERATORS_MACRO_BINARY = os.linesep.join (gen)

		genu = (cls.inline_unary_fmt_out.format (**data) for data in unary_ops)
		genb = (cls.inline_binary_fmt_out.format (**data) for data in binary_ops)
		conf.env.OPERATORS_FCT_DEF = os.linesep.join (genu) + os.linesep + os.linesep.join (genb)


def options (opt):
	pass # nothing to do

######################
##### configure ######
######################
def configure (conf):
	# add operators headers
	for headerfile in conf.path.ant_glob ("**/ibex_*.h"):
		Operator (headerfile)

	Operator.set_env (conf)
